/*
 * Copyright 2000-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.vacoor.nothing.demo;

//import com.intellij.openapi.application.PathManager;
import com.google.common.base.Joiner;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import org.vacoor.nothing.common.util.IOUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
public class Restarter {
    private Restarter() {
    }

    private static int getRestartCode() {
        String s = System.getProperty("jb.restart.code");
        if (s != null) {
            try {
                return Integer.parseInt(s);
            } catch (NumberFormatException ignore) {
            }
        }
        return 0;
    }

    public static boolean isSupported() {
        return getRestartCode() != 0 || SystemInfo.isWindows || SystemInfo.isMac;
    }

    public static int scheduleRestart(String... beforeRestart) throws IOException {
        try {
            int restartCode = getRestartCode();
            if (restartCode != 0) {
                runCommand(beforeRestart);
                return restartCode;
            } else if (SystemInfo.isWindows) {
                restartOnWindows(beforeRestart);
                return 0;
            } else if (SystemInfo.isMac) {
                restartOnMac(beforeRestart);
                return 0;
            }
        } catch (Throwable t) {
            throw new IOException("Cannot restart application: " + t.getMessage(), t);
        }

        runCommand(beforeRestart);
        throw new IOException("Cannot restart application: not supported.");
    }

    private static void runCommand(String... beforeRestart) throws IOException {
        if (beforeRestart.length == 0) return;

        try {
            Process process = Runtime.getRuntime().exec(beforeRestart);

            Thread outThread = new Thread(new StreamRedirector(process.getInputStream(), System.out));
            Thread errThread = new Thread(new StreamRedirector(process.getErrorStream(), System.err));
            outThread.start();
            errThread.start();

            try {
                process.waitFor();
            } finally {
                outThread.join();
                errThread.join();
            }
        } catch (InterruptedException ignore) {
        }
    }

    private static void restartOnWindows(final String... beforeRestart) throws IOException {
        Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
        Shell32 shell32 = (Shell32) Native.loadLibrary("shell32", Shell32.class);

        final int pid = kernel32.GetCurrentProcessId();
        final IntByReference argc = new IntByReference();
        Pointer argv_ptr = shell32.CommandLineToArgvW(kernel32.GetCommandLineW(), argc);
        final String[] argv = argv_ptr.getStringArray(0, argc.getValue(), true);
        kernel32.LocalFree(argv_ptr);

        doScheduleRestart(new File(PathManager.getBinPath(), "restarter.exe"), new Consumer<List<String>>() {
            @Override
            public void consume(List<String> commands) {
                Collections.addAll(commands, String.valueOf(pid), String.valueOf(beforeRestart.length));
                Collections.addAll(commands, beforeRestart);
                Collections.addAll(commands, String.valueOf(argc.getValue()));
                Collections.addAll(commands, argv);
            }
        });

        // Since the process ID is passed through the command line, we want to make sure that we don't exit before the "restarter"
        // process has a chance to open the handle to our process, and that it doesn't wait for the termination of an unrelated
        // process which happened to have the same process ID.
        try {
            Thread.sleep(500);
        } catch (InterruptedException ignore) {
        }
    }

    private static void restartOnMac( final String... beforeRestart) throws IOException {
        final String homePath = PathManager.getHomePath().substring(0, PathManager.getHomePath().indexOf(".app") + 4);
        if (!StringUtil.endsWithIgnoreCase(homePath, ".app"))
            throw new IOException("Application bundle not found: " + homePath);

        doScheduleRestart(new File(PathManager.getBinPath(), "restarter"), new Consumer<List<String>>() {
            @Override
            public void consume(List<String> commands) {
                Collections.addAll(commands, homePath);
                Collections.addAll(commands, beforeRestart);
            }
        });
    }

    private static void doScheduleRestart(File restarterFile, Consumer<List<String>> argumentsBuilder) throws IOException {
        List<String> commands = new ArrayList<String>();
        commands.add(createTempExecutable(restarterFile).getPath());
        argumentsBuilder.consume(commands);
        System.out.println(Joiner.on(" ").join(commands));
        Runtime.getRuntime().exec(commands.toArray(new String[commands.size()]));
    }

    public static File createTempExecutable(File executable) throws IOException {
        File executableDir = new File(System.getProperty("user.home") + "/." + System.getProperty("idea.paths.selector") + "/restart");
        File copy = new File(executableDir.getPath() + "/" + executable.getName());
        if (!FileUtilRt.ensureCanCreateFile(copy)) {
            String ext = FileUtilRt.getExtension(executable.getName());
            copy = FileUtilRt.createTempFile(executableDir, FileUtilRt.getNameWithoutExtension(copy.getName()),
                    StringUtil.isEmptyOrSpaces(ext) ? ".tmp" : ("." + ext),
                    true, false);
        }
        FileUtilRt.copy(executable, copy);
        if (!copy.setExecutable(executable.canExecute())) throw new IOException("Cannot make file executable: " + copy);
        return copy;
    }

    private interface Kernel32 extends StdCallLibrary {
        int GetCurrentProcessId();

        WString GetCommandLineW();

        Pointer LocalFree(Pointer pointer);
    }

    private interface Shell32 extends StdCallLibrary {
        Pointer CommandLineToArgvW(WString command_line, IntByReference argc);
    }

    private static class StreamRedirector implements Runnable {
        private final InputStream myIn;
        private final OutputStream myOut;

        private StreamRedirector(InputStream in, OutputStream out) {
            myIn = in;
            myOut = out;
        }

        @Override
        public void run() {
            try {
                // StreamUtil.copyStreamContent(myIn, myOut);
                IOUtils.flow(myIn, myOut, false, false);
            } catch (IOException ignore) {
            }
        }
    }
}